package com.github.yoojia.flow.impl;

import com.github.yoojia.flow.*;
import com.parkingwang.lang.data.Config;
import com.pi4j.io.serial.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;

/**
 * @author 陈小锅 (yoojiachen@gmail.com)
 * @since 1.0.0
 */
public class PiSerialDevice extends VirtualDevice {

    private static final Logger LOGGER = LoggerFactory.getLogger(PiSerialDevice.class);

    public static final String TOPIC = "SERIAL-COM";

    private Serial mSerial;
    private SerialConfig mConfig;

    @Override
    public void onInit(Context context, Config configs) throws Exception{
        super.onInit(context, configs);
        mSerial = SerialFactory.createInstance();
        mConfig = new SerialConfig();
        final String portAddress;
        try {
            portAddress = configs.getString(PiConfig.NAME_PORT_ADDRESS, SerialPort.getDefaultPort());
        } catch (Exception e) {
            final String message = traceableMessage("Failed to get default SerialCom Port");
            LOGGER.error(message, e);
            throw new RuntimeException(message);
        }
        final int baudRate = configs.getInt("baud-rate", Baud._38400.getValue());
        final int dataBits = configs.getInt("data-bits", DataBits._8.getValue());
        final int stopBits = configs.getInt("stop-bits", StopBits._1.getValue());
        final String parity = configs.getString("parity", Parity.NONE.name());
        final String flowCtrl = configs.getString("flow-control", FlowControl.NONE.name());

        LOGGER.debug("Init PiSerialDevice: Port={}, baud={}, dataBits={}, parity={}, stopBits={}, flowCtrl={}",
                portAddress, baudRate, dataBits, parity, stopBits, flowCtrl);

        mConfig.device(portAddress)
                .baud(Baud.getInstance(baudRate))
                .dataBits(DataBits.getInstance(dataBits))
                .parity(Parity.getInstance(parity))
                .stopBits(StopBits.getInstance(stopBits))
                .flowControl(FlowControl.getInstance(flowCtrl));
    }

    @Override
    public void onStart() {
        try {
            mSerial.open(mConfig);
        } catch (IOException e) {
            LOGGER.error(traceableMessage("Failed to OPEN PiSerialDevice"), e);
        }
        // 接收串口数据
        mSerial.addListener(this::onReceivedData);
    }

    protected void onReceivedData(SerialDataEvent data){
        try {
            final Payload.Bytes bytes = Payload.Bytes.create(data.getBytes());
            LOGGER.debug("Serial COM received data: {}", bytes.getHexString());
            Event event = Event.createNoneDestAddress(
                    getContext().nextSequenceId(),
                    bytes.getBytes(),
                    TOPIC,
                    getAddress(),
                    System.currentTimeMillis());
            if (isLoggingEnabled()){
                LOGGER.debug("Publishing Frame Event: {}", event);
            }
            postEvent(event);
        } catch (Exception e) {
            LOGGER.error(traceableMessage("Error when received data from PiSerialDevice"), e);
        }
    }

    @Override
    public void onStop() {
        if (mSerial != null && mSerial.isOpen()) {
            try {
                mSerial.close();
            } catch (IOException e) {
                LOGGER.error(traceableMessage("Failed to CLOSE PiSerialDevice"), e);
            }
        }
    }

    @Override
    public Event handleEvent(Context context, Event request) {
        // 接收到数据时，将数据写入串口
        if (isLoggingEnabled()){
            LOGGER.debug("Received Frame Event to write: {}", request);
        }
        final Payload.Bytes bytes = request.getBytesPayload();
        if (mSerial.isOpen()){
            try {
                mSerial.write(bytes.getBytes());
            } catch (IOException e) {
                LOGGER.error(traceableMessage("Error when write bytes data to PiSerialDevice"), e);
            }
        }
        return Event.simple(request.sequence, Context.TOPIC_SYS_NORESP);
    }
}
